/**
 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "compiler_base_types.h"

#include "compiler/optimizer/ir/datatype.h"
#include "compiler/optimizer/code_generator/codegen.h"
#include "compiler/optimizer/code_generator/encode_visitor.h"
#include "ir-dyn-base-types.h"
#include "runtime/include/coretypes/tagged_value.h"
#include "profiling/profiling.h"

namespace ark::compiler {

static void IsHasTagMaskGen(Encoder *enc, Reg dst, Reg src, Imm valTag, Imm valMask,
                            LabelHolder::LabelId id = LabelHolder::INVALID_LABEL)
{
    ScopedTmpReg regValTag(enc, TypeInfo(TypeInfo::TypeId::INT64));
    auto dstExt = dst.As(TypeInfo(TypeInfo::TypeId::INT64));

    enc->EncodeMov(regValTag, valTag);
    if (valTag != valMask) {
        enc->EncodeAnd(dstExt, src, valMask);
    } else {
        enc->EncodeAnd(dstExt, src, regValTag);
    }
    if (id == LabelHolder::INVALID_LABEL) {
        enc->EncodeCompare(dst, dstExt, regValTag, Condition::EQ);
    } else {
        enc->EncodeJump(id, dstExt, regValTag, Condition::NE);
    }
}

static void CompareAnyTypeGenDouble(Encoder *enc, Reg dst, Reg src, profiling::AnyInputType allowedInputType,
                                    LabelHolder::LabelId notNumberLabel = LabelHolder::INVALID_LABEL,
                                    LabelHolder::LabelId doubleWithIntLabel = LabelHolder::INVALID_LABEL)
{
    static constexpr uint16_t CMP_VAL = static_cast<uint16_t>(UINT16_MAX) >> 1U;  // 0x7fff
    static constexpr uint8_t SHIFT_VAL = coretypes::TaggedValue::TAG_BITS_SHIFT + 1;

    auto dstExt = dst.As(TypeInfo(TypeInfo::TypeId::INT64));

    auto jump = notNumberLabel != LabelHolder::INVALID_LABEL;
    ASSERT(allowedInputType == profiling::AnyInputType::DEFAULT || jump);
    if (allowedInputType == profiling::AnyInputType::SPECIAL_INT) {
        constexpr auto MIN_OBJECT = coretypes::TaggedValue::VALUE_UNDEFINED + 1;
        constexpr auto MIN_NUMBER = 1ULL << coretypes::TaggedValue::TAG_BITS_SHIFT;
        static_assert(coretypes::TaggedValue::VALUE_TRUE < MIN_OBJECT);
        static_assert(coretypes::TaggedValue::TAG_EXCEPTION >= MIN_OBJECT);
        enc->EncodeSub(dstExt, src, Imm(MIN_OBJECT));
        enc->EncodeJump(notNumberLabel, dstExt, Imm(MIN_NUMBER - MIN_OBJECT), Condition::LO);
        return;
    }
    if (allowedInputType == profiling::AnyInputType::INTEGER) {
        enc->EncodeShr(dstExt, src, Imm(coretypes::TaggedValue::TAG_BITS_SHIFT));
        enc->EncodeJump(notNumberLabel, dstExt, Imm(0), Condition::EQ);
        return;
    }
    LabelHolder::LabelId endLabel = enc->CreateLabel();
    if (allowedInputType == profiling::AnyInputType::SPECIAL) {
        enc->EncodeJump(endLabel, src, Imm(coretypes::TaggedValue::VALUE_UNDEFINED), Condition::EQ);
    } else {
        ASSERT(allowedInputType == profiling::AnyInputType::DEFAULT);
    }
    // (u16) tag + 0xffff is [0xffff for Object, 0xfffe for Int, other - Double]
    enc->EncodeAdd(dstExt, src, Imm(ark::coretypes::TaggedValue::TAG_MASK));
    // Examine tag and shift it by one -> 0x7fff for both Object and Int
    enc->EncodeShr(dstExt, dstExt, Imm(SHIFT_VAL));
    ScopedTmpReg regCmp(enc, TypeInfo(TypeInfo::TypeId::INT64));
    enc->EncodeMov(regCmp, Imm(CMP_VAL));
    // check if not Object and not Int
    if (!jump) {
        enc->EncodeCompare(dst, dstExt, regCmp, Condition::LO);
    } else if (doubleWithIntLabel != LabelHolder::INVALID_LABEL) {
        enc->EncodeJump(endLabel, dstExt, regCmp, Condition::LT);
        auto bit = coretypes::TaggedValue::TAG_BITS_SIZE + coretypes::TaggedValue::TAG_BITS_SHIFT - 1;
        enc->EncodeBitTestAndBranch(doubleWithIntLabel, src, bit, true);
        enc->EncodeJump(notNumberLabel);
    } else {
        enc->EncodeJump(notNumberLabel, dstExt, regCmp, Condition::GE);
    }
    enc->BindLabel(endLabel);
}

static void CompareAnyTypeGenInt(Encoder *enc, Reg dst, Reg src, profiling::AnyInputType allowedInputType,
                                 LabelHolder::LabelId id = LabelHolder::INVALID_LABEL)
{
    if (allowedInputType == profiling::AnyInputType::DEFAULT) {
        IsHasTagMaskGen(enc, dst, src, Imm(ark::coretypes::TaggedValue::TAG_INT),
                        Imm(ark::coretypes::TaggedValue::TAG_MASK), id);
        return;
    }
    ASSERT(allowedInputType == profiling::AnyInputType::SPECIAL);
    constexpr auto MIN_FAIL = coretypes::TaggedValue::VALUE_UNDEFINED;
    // accept values < VALUE_UNDEFINED or >= TAG_INT
    auto dstExt = dst.As(TypeInfo(TypeInfo::TypeId::INT64));
    enc->EncodeSub(dstExt, src, Imm(MIN_FAIL));
    if (id == LabelHolder::INVALID_LABEL) {
        ScopedTmpReg regCmp(enc, TypeInfo(TypeInfo::TypeId::INT64));
        enc->EncodeMov(regCmp, Imm(coretypes::TaggedValue::TAG_INT - MIN_FAIL));
        enc->EncodeCompare(dstExt, dstExt, regCmp, Condition::HS);
    } else {
        enc->EncodeJump(id, dstExt, Imm(coretypes::TaggedValue::TAG_INT - MIN_FAIL), Condition::LO);
    }
}

static void CompareAnyTypeGenBool(Encoder *enc, Reg dst, Reg src, LabelHolder::LabelId id = LabelHolder::INVALID_LABEL)
{
    static constexpr uint64_t BOOL_DIFF_MASK =
        ~(ark::coretypes::TaggedValue::VALUE_FALSE ^ ark::coretypes::TaggedValue::VALUE_TRUE);
    static constexpr uint64_t BOOL_MASK =
        ark::coretypes::TaggedValue::VALUE_FALSE & ark::coretypes::TaggedValue::VALUE_TRUE;

    ScopedTmpReg regCmp(enc, TypeInfo(TypeInfo::TypeId::INT64));
    auto dstExt = dst.As(TypeInfo(TypeInfo::TypeId::INT64));

    enc->EncodeAnd(dstExt, src, Imm(BOOL_DIFF_MASK));
    enc->EncodeMov(regCmp, Imm(BOOL_MASK));
    if (id == LabelHolder::INVALID_LABEL) {
        enc->EncodeCompare(dst, dstExt, regCmp, Condition::EQ);
    } else {
        enc->EncodeJump(id, dstExt, regCmp, Condition::NE);
    }
}

static void IsEqualToValGen(Encoder *enc, Reg dst, Reg src, Imm val,
                            LabelHolder::LabelId id = LabelHolder::INVALID_LABEL)
{
    if (id == LabelHolder::INVALID_LABEL) {
        ScopedTmpReg regVal(enc, TypeInfo(TypeInfo::TypeId::INT64));
        enc->EncodeMov(regVal, val);
        enc->EncodeCompare(dst, src, regVal, Condition::EQ);
    } else {
        enc->EncodeJump(id, src, val, Condition::NE);
    }
}

// Jump out if !IsHeapObject()
static void CheckAnyTypeGenObject(Encoder *enc, const Reg &src, LabelHolder::LabelId label)
{
    ScopedTmpReg tmpReg(enc, TypeInfo(TypeInfo::TypeId::INT64));

    // IsObject()
    enc->EncodeAnd(tmpReg, src, Imm(ark::coretypes::TaggedValue::TAG_MASK));
    enc->EncodeJump(label, tmpReg, Imm(ark::coretypes::TaggedValue::TAG_OBJECT), Condition::NE);

    // !IsSpecial
    // It's enough to check that `src` > `TAG_SPECIAL_MASK`, as it's guaranteed that special values
    // are not in object address space
    ASSERT(!IsAddressInObjectsHeapOrNull(ark::coretypes::TaggedValue::TAG_SPECIAL_MASK));
    enc->EncodeJump(label, src, Imm(ark::coretypes::TaggedValue::TAG_SPECIAL_MASK), Condition::LE);
}

// Jump out if (!IsHeapObject() or <fail_cc> object.type, type)
static void CheckAnyTypeGenObjectType(Codegen *codegen, Encoder *enc, const Reg &src, uint32_t type,
                                      LabelHolder::LabelId label, Condition failCc = Condition::NE)
{
    CheckAnyTypeGenObject(enc, src, label);

    auto arch = codegen->GetArch();
    ScopedTmpReg tmpReg(enc, codegen->ConvertDataType(DataType::REFERENCE, arch));

    enc->EncodeLdr(tmpReg, false, MemRef(src, codegen->GetRuntime()->GetObjClassOffset(arch)));
    auto typeStartBit = cross_values::GetJshclassBitfieldTypeStartBit(arch);
    auto bitMask = static_cast<uint64_t>(cross_values::GetJshclassBitfieldTypeMask(arch));
    enc->EncodeLdr(tmpReg, false, MemRef(tmpReg, cross_values::GetHclassDataOffset(arch)));
    if (typeStartBit != 0) {
        enc->EncodeShr(tmpReg, tmpReg, Imm(typeStartBit));
    }
    enc->EncodeAnd(tmpReg, tmpReg, Imm(bitMask));
    enc->EncodeJump(label, tmpReg, Imm(type), failCc);
}

// dst = IsHeapObject()
static void CompareAnyTypeGenObject(Encoder *enc, const Reg &dst, const Reg &src)
{
    auto labelFalse = enc->CreateLabel();
    enc->EncodeMov(dst, Imm(0));
    CheckAnyTypeGenObject(enc, src, labelFalse);
    enc->EncodeMov(dst, Imm(1U));
    enc->BindLabel(labelFalse);
}

static void CompareOrCheckAnyTypeCallable(Codegen *codegen, Encoder *enc, const Reg &dst, const Reg &src,
                                          LabelHolder::LabelId id = LabelHolder::INVALID_LABEL)
{
    auto labelFalse = enc->CreateLabel();
    auto arch = codegen->GetArch();
    Reg resReg = dst;
    bool needTemp = (dst.GetId() == src.GetId());
    ScopedTmpRegLazy tmpDst(enc);
    if (needTemp) {
        tmpDst.AcquireIfInvalid();
        resReg = tmpDst.GetReg().As(codegen->ConvertDataType(DataType::ANY, arch));
    }

    if (id == LabelHolder::INVALID_LABEL) {
        enc->EncodeMov(resReg, Imm(0));
    }
    LabelHolder::LabelId label = (id == LabelHolder::INVALID_LABEL) ? labelFalse : id;
    CheckAnyTypeGenObject(enc, src, label);
    ScopedTmpReg tmpReg(enc, codegen->ConvertDataType(DataType::REFERENCE, arch));

    enc->EncodeLdr(tmpReg, false, MemRef(src, codegen->GetRuntime()->GetObjClassOffset(arch)));
    Reg tmp32Reg = tmpReg.GetReg().As(TypeInfo(TypeInfo::TypeId::INT32));
    enc->EncodeLdr(tmp32Reg, false, MemRef(tmpReg, codegen->GetRuntime()->GetBaseClassFlagsOffset(arch)));
    auto isCallableMask = codegen->GetRuntime()->GetCallableMask();
    enc->EncodeJumpTest(label, tmp32Reg, Imm(isCallableMask), Condition::TST_EQ);
    if (id == LabelHolder::INVALID_LABEL) {
        enc->EncodeMov(resReg, Imm(1U));
        enc->BindLabel(labelFalse);
        if (needTemp) {
            enc->EncodeMov(dst, resReg);
        }
    }
}

// dst = (IsHeapObject() and !<fail_cc> object.type, type)
static void CompareAnyTypeGenObjectType(Codegen *codegen, Encoder *enc, const Reg &dst, const Reg &src, uint32_t type,
                                        Condition failCc = Condition::NE)
{
    Reg resReg = dst;
    auto arch = codegen->GetArch();
    bool needTemp = (dst.GetId() == src.GetId());
    ScopedTmpRegLazy tmpReg(enc);
    if (needTemp) {
        tmpReg.AcquireIfInvalid();
        resReg = tmpReg.GetReg().As(codegen->ConvertDataType(DataType::ANY, arch));
    }
    auto labelFalse = enc->CreateLabel();
    enc->EncodeMov(resReg, Imm(0));
    CheckAnyTypeGenObjectType(codegen, enc, src, type, labelFalse, failCc);
    enc->EncodeMov(resReg, Imm(1U));
    enc->BindLabel(labelFalse);
    if (needTemp) {
        enc->EncodeMov(dst, resReg);
    }
}

bool ecmascript::GetAnyTypeNameGen(const GetAnyTypeNameInst *inst, EncodeVisitor *encV)
{
    Encoder *enc = encV->GetEncoder();
    Codegen *codegen = encV->GetCodegen();
    auto dst = codegen->ConvertRegister(inst->GetDstReg(), DataType::Type::ANY);

    auto graph = codegen->GetGraph();
    auto offset = graph->GetRuntime()->GetGlobalConstStringOffsetForAnyType(inst->GetAnyType(), graph->GetArch());
    enc->EncodeLdr(dst, false, MemRef(codegen->ThreadReg(), offset));

    return true;
}

bool ecmascript::CompareAnyTypeGen(const CompareAnyTypeInst *cati, EncodeVisitor *encV)
{
    auto *codegen = encV->GetCodegen();
    auto dst = encV->GetCodegen()->ConvertRegister(cati->GetDstReg(), DataType::Type::BOOL);
    auto src = encV->GetCodegen()->ConvertRegister(cati->GetSrcReg(0), DataType::Type::INT64);

    Encoder *enc = encV->GetEncoder();
    switch (cati->GetAnyType()) {
        case AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE:
            IsEqualToValGen(enc, dst, src, Imm(ark::coretypes::TaggedValue::VALUE_UNDEFINED));
            return true;
        case AnyBaseType::ECMASCRIPT_INT_TYPE:
            CompareAnyTypeGenInt(enc, dst, src, cati->GetAllowedInputType());
            return true;
        case AnyBaseType::ECMASCRIPT_OBJECT_TYPE:
            IsHasTagMaskGen(enc, dst, src, Imm(ark::coretypes::TaggedValue::TAG_OBJECT),
                            Imm(ark::coretypes::TaggedValue::TAG_MASK));
            return true;
        case AnyBaseType::ECMASCRIPT_HEAP_OBJECT_TYPE:
            CompareAnyTypeGenObject(enc, dst, src);
            return true;
        case AnyBaseType::ECMASCRIPT_DOUBLE_TYPE:
            CompareAnyTypeGenDouble(enc, dst, src, cati->GetAllowedInputType());
            return true;
        case AnyBaseType::ECMASCRIPT_STRING_TYPE:
            CompareAnyTypeGenObjectType(codegen, enc, dst, src, cross_values::GetJstypeString(codegen->GetArch()));
            return true;
        case AnyBaseType::ECMASCRIPT_SYMBOL_TYPE:
            CompareAnyTypeGenObjectType(codegen, enc, dst, src, cross_values::GetJstypeSymbol(codegen->GetArch()));
            return true;
        case AnyBaseType::ECMASCRIPT_BIGINT_TYPE:
            CompareAnyTypeGenObjectType(codegen, enc, dst, src, cross_values::GetJstypeBigint(codegen->GetArch()));
            return true;
        case AnyBaseType::ECMASCRIPT_ARRAY_TYPE:
            CompareAnyTypeGenObjectType(codegen, enc, dst, src, cross_values::GetJstypeJsArray(codegen->GetArch()));
            return true;
        case AnyBaseType::ECMASCRIPT_TRANSITION_HANDLER_TYPE:
            CompareAnyTypeGenObjectType(codegen, enc, dst, src,
                                        cross_values::GetJstypeTransitionHandler(codegen->GetArch()));
            return true;
        case AnyBaseType::ECMASCRIPT_PROTOTYPE_HANDLER_TYPE:
            CompareAnyTypeGenObjectType(codegen, enc, dst, src,
                                        cross_values::GetJstypePrototypeHandler(codegen->GetArch()));
            return true;
        case AnyBaseType::ECMASCRIPT_SPECIAL_INDEXED_TYPE:
            CompareAnyTypeGenObjectType(codegen, enc, dst, src, cross_values::GetJstypeJsArray(codegen->GetArch()),
                                        Condition::LE);
            return true;
        case AnyBaseType::ECMASCRIPT_FUNCTION_TYPE:
            CompareAnyTypeGenObjectType(codegen, enc, dst, src, cross_values::GetJstypeJsFunction(codegen->GetArch()));
            return true;
        case AnyBaseType::ECMASCRIPT_CALLABLE_TYPE:
            CompareOrCheckAnyTypeCallable(codegen, enc, dst, src);
            return true;
        case AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE: {
            CompareAnyTypeGenBool(enc, dst, src);
            return true;
        }
        case AnyBaseType::ECMASCRIPT_NULL_TYPE:
            IsEqualToValGen(enc, dst, src, Imm(ark::coretypes::TaggedValue::VALUE_NULL));
            return true;
        case AnyBaseType::ECMASCRIPT_HOLE_TYPE:
            IsEqualToValGen(enc, dst, src, Imm(ark::coretypes::TaggedValue::VALUE_HOLE));
            return true;
        default:
            return false;
    }

    return false;
}

// NOLINTNEXTLINE(readability-function-size)
bool ecmascript::CastAnyTypeValueGen(const CastAnyTypeValueInst *cati, EncodeVisitor *encV)
{
    auto dstRegType = cati->GetType();
    dstRegType = DataType::IsFloatType(dstRegType) ? DataType::FLOAT64 : dstRegType;
    auto dst = encV->GetCodegen()->ConvertRegister(cati->GetDstReg(), dstRegType);
    auto src = encV->GetCodegen()->ConvertRegister(cati->GetSrcReg(0), DataType::INT64);

    auto *enc = encV->GetEncoder();
    switch (cati->GetAnyType()) {
        case AnyBaseType::ECMASCRIPT_NULL_TYPE:
        case AnyBaseType::ECMASCRIPT_HOLE_TYPE:
        case AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE:
            enc->EncodeMov(dst, Imm(0));
            return true;
        case AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE:
            enc->EncodeAnd(dst, src, Imm(1));
            return true;
        case AnyBaseType::ECMASCRIPT_INT_TYPE: {
            if (DataType::GetTypeSize(dstRegType, RUNTIME_ARCH) >
                DataType::GetTypeSize(DataType::INT32, RUNTIME_ARCH)) {
                ASSERT(false && "sign-extension by dst type is not supported");
            }
            Reg resReg = src;
            ScopedTmpRegLazy tmpDst(enc);
            if (cati->GetAllowedInputType() == profiling::AnyInputType::SPECIAL) {
                if (dst.GetId() == src.GetId()) {
                    tmpDst.AcquireIfInvalid();
                    resReg = tmpDst;
                } else {
                    resReg = dst.As(INT64_TYPE);
                }
                enc->EncodeAShr(resReg, src, Imm(BitNumbers<coretypes::TaggedType>() - 1));
                enc->EncodeOr(resReg, resReg, Imm(1));
                // here res_reg contains 1 for special values and -1 for tagged ints
                enc->EncodeAnd(resReg, resReg, src);
            }
            ASSERT(resReg.GetSize() == DOUBLE_WORD_SIZE);
            ASSERT(!dst.IsFloat() && !src.IsFloat());
            enc->EncodeCast(dst, false, resReg, false);
            return true;
        }
        case AnyBaseType::ECMASCRIPT_DOUBLE_TYPE: {
            ASSERT(dst.IsFloat());
            ScopedTmpReg regTmp(enc, TypeInfo(TypeInfo::INT64));
            auto endLabel = enc->CreateLabel();
            auto returnLabel = enc->CreateLabel();
            if ((cati->GetAllowedInputType() & profiling::AnyInputType::SPECIAL) != 0) {
                // We allow Hole value in AnyTypeCheck with SPECIAL flag, but assume it cannot be
                // CastValueToAnyType input, so it is cast to 0 instead of NaN here
                SCOPED_DISASM_STR(encV->GetCodegen(), "Try cast from Undefined");
                enc->EncodeMov(regTmp,
                               Imm(coretypes::ReinterpretDoubleToTaggedType(coretypes::TaggedValue::VALUE_NAN)));
                enc->EncodeJump(returnLabel, src, Imm(coretypes::TaggedValue::VALUE_UNDEFINED), Condition::EQ);
            }
            if (cati->GetAllowedInputType() == profiling::AnyInputType::SPECIAL_INT) {
                SCOPED_DISASM_STR(encV->GetCodegen(), "Try cast from Boolean or Null");
                auto intDoubleLabel = enc->CreateLabel();
                enc->EncodeShr(regTmp, src, Imm(coretypes::TaggedValue::TAG_BITS_SHIFT));
                enc->EncodeJump(intDoubleLabel, regTmp, Imm(0), Condition::NE);
                enc->EncodeAnd(dst.As(INT32_TYPE), src, Imm(1));
                enc->EncodeCast(dst.As(FLOAT64_TYPE), true, dst.As(INT32_TYPE), true);
                enc->EncodeJump(endLabel);
                enc->BindLabel(intDoubleLabel);
            }
            if ((cati->GetAllowedInputType() & profiling::AnyInputType::INTEGER) != 0) {
                SCOPED_DISASM_STR(encV->GetCodegen(), "Try cast from SMI");
                auto notIntLabel = enc->CreateLabel();
                enc->EncodeShr(regTmp, src, Imm(coretypes::TaggedValue::TAG_BITS_SHIFT));
                enc->EncodeJump(notIntLabel, regTmp,
                                Imm(coretypes::TaggedValue::TAG_INT >> coretypes::TaggedValue::TAG_BITS_SHIFT),
                                Condition::NE);
                enc->EncodeCast(dst.As(FLOAT64_TYPE), true, src.As(INT32_TYPE), true);
                enc->EncodeJump(endLabel);
                enc->BindLabel(notIntLabel);
            }
            enc->EncodeSub(regTmp, src, Imm(ark::coretypes::TaggedValue::DOUBLE_ENCODE_OFFSET));
            enc->BindLabel(returnLabel);
            enc->EncodeMov(dst, regTmp);
            enc->BindLabel(endLabel);
            return true;
        }
        case AnyBaseType::ECMASCRIPT_OBJECT_TYPE:
        case AnyBaseType::ECMASCRIPT_HEAP_OBJECT_TYPE:
        case AnyBaseType::ECMASCRIPT_STRING_TYPE:
        case AnyBaseType::ECMASCRIPT_SYMBOL_TYPE:
        case AnyBaseType::ECMASCRIPT_BIGINT_TYPE:
        case AnyBaseType::ECMASCRIPT_ARRAY_TYPE:
        case AnyBaseType::ECMASCRIPT_TRANSITION_HANDLER_TYPE:
        case AnyBaseType::ECMASCRIPT_PROTOTYPE_HANDLER_TYPE:
        case AnyBaseType::ECMASCRIPT_FUNCTION_TYPE:
        case AnyBaseType::ECMASCRIPT_CALLABLE_TYPE:
        case AnyBaseType::ECMASCRIPT_SPECIAL_INDEXED_TYPE: {
            enc->EncodeMov(dst, src);
            return true;
        }
        default:
            return false;
    }

    return false;
}

bool ecmascript::CastValueToAnyTypeGen(const CastValueToAnyTypeInst *cvai, EncodeVisitor *encV)
{
    auto srcRegType = cvai->GetInputType(0);
    srcRegType = DataType::IsFloatType(srcRegType) ? DataType::FLOAT64 : srcRegType;
    auto dst = encV->GetCodegen()->ConvertRegister(cvai->GetDstReg(), DataType::INT64);
    auto src = encV->GetCodegen()->ConvertRegister(cvai->GetSrcReg(0), srcRegType);

    auto *enc = encV->GetEncoder();
    switch (cvai->GetAnyType()) {
        case AnyBaseType::ECMASCRIPT_NULL_TYPE:
            enc->EncodeMov(dst, Imm(ark::coretypes::TaggedValue::VALUE_NULL));
            return true;
        case AnyBaseType::ECMASCRIPT_HOLE_TYPE:
            enc->EncodeMov(dst, Imm(ark::coretypes::TaggedValue::VALUE_HOLE));
            return true;
        case AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE:
            enc->EncodeMov(dst, Imm(ark::coretypes::TaggedValue::VALUE_UNDEFINED));
            return true;
        case AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE:
            enc->EncodeOr(dst, src, Imm(ark::coretypes::TaggedValue::VALUE_FALSE));
            return true;
        case AnyBaseType::ECMASCRIPT_INT_TYPE: {
            auto src32 = encV->GetCodegen()->ConvertRegister(cvai->GetSrcReg(0), DataType::INT32);
            enc->EncodeCast(dst, false, src32, false);
            enc->EncodeOr(dst, dst, Imm(ark::coretypes::TaggedValue::TAG_INT));
            return true;
        }
        case AnyBaseType::ECMASCRIPT_DOUBLE_TYPE:
            enc->EncodeMov(dst, src);
            enc->EncodeAdd(dst, dst, Imm(ark::coretypes::TaggedValue::DOUBLE_ENCODE_OFFSET));
            return true;
        case AnyBaseType::ECMASCRIPT_STRING_TYPE:
        case AnyBaseType::ECMASCRIPT_SYMBOL_TYPE:
        case AnyBaseType::ECMASCRIPT_BIGINT_TYPE:
        case AnyBaseType::ECMASCRIPT_OBJECT_TYPE:
        case AnyBaseType::ECMASCRIPT_HEAP_OBJECT_TYPE:
        case AnyBaseType::ECMASCRIPT_ARRAY_TYPE:
        case AnyBaseType::ECMASCRIPT_TRANSITION_HANDLER_TYPE:
        case AnyBaseType::ECMASCRIPT_PROTOTYPE_HANDLER_TYPE:
        case AnyBaseType::ECMASCRIPT_FUNCTION_TYPE:
        case AnyBaseType::ECMASCRIPT_CALLABLE_TYPE:
        case AnyBaseType::ECMASCRIPT_SPECIAL_INDEXED_TYPE:
            // With current boxing scheme, we must guarantee that boxed objects have upper 16 bits set to 0.
            // Boxed values are no less than 64 bits wide (to hold double-precision floating point numbers).
            // As long as pointers are no more than 32 bits wide, a move from a 32-bit source to a 64-bit
            // destination will zero out upper 32 bits of the destination. Otherwise we fallback to the safe
            // path where we explicitly zero-out upper 16 bits by applying a mask.
            if (src.GetSize() <= WORD_SIZE) {
                ASSERT(dst.GetSize() >= DOUBLE_WORD_SIZE);
                enc->EncodeMov(dst, src);
            } else {
                enc->EncodeAnd(dst, src, Imm(ark::coretypes::TaggedValue::OBJECT_MASK));
            }
            return true;
        default:
            return false;
    }

    return false;
}

bool ecmascript::ObjByIndexCheckGen(const FixedInputsInst<2> *checkInst, EncodeVisitor *encV, LabelHolder::LabelId id)
{
    auto src = encV->GetCodegen()->ConvertRegister(checkInst->GetSrcReg(0), DataType::Type::INT64);
    Encoder *enc = encV->GetEncoder();
    Codegen *codegen = encV->GetCodegen();
    // Check that the value is Heap Object
    CheckAnyTypeGenObject(enc, src, id);

    auto arch = codegen->GetArch();
    ScopedTmpReg propertyReg(enc, codegen->ConvertDataType(DataType::INT64, arch));
    ScopedTmpReg tmpReg(enc, codegen->ConvertDataType(DataType::REFERENCE, arch));

    enc->EncodeLdr(tmpReg, false, MemRef(src, codegen->GetRuntime()->GetObjClassOffset(arch)));
    enc->EncodeLdr(propertyReg, false, MemRef(tmpReg, cross_values::GetHclassDataOffset(arch)));
    auto typeStartBit = cross_values::GetJshclassBitfieldTypeStartBit(arch);
    if (typeStartBit != 0) {
        enc->EncodeShr(propertyReg, propertyReg, Imm(typeStartBit));
    }
    // Check that te object isn't special
    enc->EncodeAnd(tmpReg, propertyReg, Imm(static_cast<uint64_t>(cross_values::GetJshclassBitfieldTypeMask(arch))));
    enc->EncodeJump(id, tmpReg, Imm(cross_values::GetJstypeJsArray(codegen->GetArch())), Condition::GT);
    auto bitOffset = static_cast<size_t>(cross_values::GetJshclassBitfieldIsDictionaryStartBit(codegen->GetArch()));
    // Check that the object isn't dictionary
    enc->EncodeJumpTest(id, tmpReg, Imm(1U << bitOffset), Condition::TST_NE);
    return true;
}

static bool AnyTypeCheckGenCustomDeoptimization(Encoder *enc, Codegen *codegen, AnyTypeCheckInst *checkInst, Reg src)
{
    ScopedTmpReg tmpReg(enc, TypeInfo(TypeInfo::TypeId::INT64));
    switch (checkInst->GetAnyType()) {
        case AnyBaseType::ECMASCRIPT_INT_TYPE: {
            auto id = codegen->CreateSlowPath<SlowPathDeoptimize>(checkInst, DeoptimizeType::NOT_SMALL_INT)->GetLabel();
            CompareAnyTypeGenInt(enc, tmpReg, src, checkInst->GetAllowedInputType(), id);
            return true;
        }
        case AnyBaseType::ECMASCRIPT_DOUBLE_TYPE: {
            auto notNumber =
                codegen->CreateSlowPath<SlowPathDeoptimize>(checkInst, DeoptimizeType::NOT_NUMBER)->GetLabel();
            auto doubleWithInt =
                codegen->CreateSlowPath<SlowPathDeoptimize>(checkInst, DeoptimizeType::DOUBLE_WITH_INT)->GetLabel();
            CompareAnyTypeGenDouble(enc, tmpReg, src, checkInst->GetAllowedInputType(), notNumber, doubleWithInt);
            return true;
        }
        default:
            return false;
    }
}

bool ecmascript::AnyTypeCheckGen(AnyTypeCheckInst *checkInst, EncodeVisitor *encV)
{
    auto src = encV->GetCodegen()->ConvertRegister(checkInst->GetSrcReg(0), DataType::Type::INT64);
    Encoder *enc = encV->GetEncoder();
    Codegen *codegen = encV->GetCodegen();

    auto graph = codegen->GetGraph();
    auto customDeoptimize = graph->IsAotMode() || graph->GetRuntime()->GetMethodProfile(graph->GetMethod(), true) !=
                                                      profiling::INVALID_PROFILE;
    // avoid excessive recompilations for methods which were created while "eval" because such methods are not profiled
    // in current implementation
    if (customDeoptimize && AnyTypeCheckGenCustomDeoptimization(enc, codegen, checkInst, src)) {
        return true;
    }

    ScopedTmpReg tmpReg(enc, TypeInfo(TypeInfo::TypeId::INT64));

    auto id = codegen->CreateSlowPath<SlowPathDeoptimize>(checkInst, DeoptimizeType::ANY_TYPE_CHECK)->GetLabel();
    switch (checkInst->GetAnyType()) {
        case AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE:
            IsEqualToValGen(enc, tmpReg, src, Imm(ark::coretypes::TaggedValue::VALUE_UNDEFINED), id);
            return true;
        case AnyBaseType::ECMASCRIPT_INT_TYPE: {
            CompareAnyTypeGenInt(enc, tmpReg, src, checkInst->GetAllowedInputType(), id);
            return true;
        }
        case AnyBaseType::ECMASCRIPT_OBJECT_TYPE:
            IsHasTagMaskGen(enc, tmpReg, src, Imm(ark::coretypes::TaggedValue::TAG_OBJECT),
                            Imm(ark::coretypes::TaggedValue::TAG_MASK), id);
            return true;
        case AnyBaseType::ECMASCRIPT_HEAP_OBJECT_TYPE:
            CheckAnyTypeGenObject(enc, src, id);
            return true;
        case AnyBaseType::ECMASCRIPT_DOUBLE_TYPE:
            CompareAnyTypeGenDouble(enc, tmpReg, src, checkInst->GetAllowedInputType(), id);
            return true;
        case AnyBaseType::ECMASCRIPT_STRING_TYPE:
            CheckAnyTypeGenObjectType(codegen, enc, src, cross_values::GetJstypeString(codegen->GetArch()), id);
            return true;
        case AnyBaseType::ECMASCRIPT_SYMBOL_TYPE:
            CompareAnyTypeGenObjectType(codegen, enc, tmpReg, src, cross_values::GetJstypeSymbol(codegen->GetArch()));
            return true;
        case AnyBaseType::ECMASCRIPT_BIGINT_TYPE:
            CheckAnyTypeGenObjectType(codegen, enc, src, cross_values::GetJstypeBigint(codegen->GetArch()), id);
            return true;
        case AnyBaseType::ECMASCRIPT_ARRAY_TYPE:
            CheckAnyTypeGenObjectType(codegen, enc, src, cross_values::GetJstypeJsArray(codegen->GetArch()), id);
            return true;
        case AnyBaseType::ECMASCRIPT_TRANSITION_HANDLER_TYPE:
            CheckAnyTypeGenObjectType(codegen, enc, src, cross_values::GetJstypeTransitionHandler(codegen->GetArch()),
                                      id);
            return true;
        case AnyBaseType::ECMASCRIPT_PROTOTYPE_HANDLER_TYPE:
            CheckAnyTypeGenObjectType(codegen, enc, src, cross_values::GetJstypePrototypeHandler(codegen->GetArch()),
                                      id);
            return true;
        case AnyBaseType::ECMASCRIPT_SPECIAL_INDEXED_TYPE:
            CheckAnyTypeGenObjectType(codegen, enc, src, cross_values::GetJstypeJsArray(codegen->GetArch()), id,
                                      Condition::LE);
            return true;
        case AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE: {
            CompareAnyTypeGenBool(enc, tmpReg, src, id);
            return true;
        }
        case AnyBaseType::ECMASCRIPT_NULL_TYPE:
            IsEqualToValGen(enc, tmpReg, src, Imm(ark::coretypes::TaggedValue::VALUE_NULL), id);
            return true;
        case AnyBaseType::ECMASCRIPT_HOLE_TYPE:
            IsEqualToValGen(enc, tmpReg, src, Imm(ark::coretypes::TaggedValue::VALUE_HOLE), id);
            return true;
        case AnyBaseType::ECMASCRIPT_FUNCTION_TYPE:
            CheckAnyTypeGenObjectType(codegen, enc, src, cross_values::GetJstypeJsFunction(codegen->GetArch()), id);
            return true;
        case AnyBaseType::ECMASCRIPT_CALLABLE_TYPE:
            CompareOrCheckAnyTypeCallable(codegen, enc, INVALID_REGISTER, src, id);
            return true;
        default:
            return false;
    }

    return false;
}

bool ecmascript::LoadConstantPoolGen(const Inst *inst, EncodeVisitor *encV)
{
    auto *codegen = encV->GetCodegen();
    auto src = codegen->ConvertRegister(inst->GetSrcReg(0), inst->GetType());
    auto dst = codegen->ConvertRegister(inst->GetDstReg(), inst->GetType());
    codegen->GetEncoder()->EncodeLdr(dst, false,
                                     MemRef(src, cross_values::GetJsfunctionConstantPoolOffset(codegen->GetArch())));
    return true;
}

bool ecmascript::LoadLexicalEnvGen(const Inst *inst, EncodeVisitor *encV)
{
    auto *codegen = encV->GetCodegen();
    auto src = codegen->ConvertRegister(inst->GetSrcReg(0), inst->GetType());
    auto dst = codegen->ConvertRegister(inst->GetDstReg(), inst->GetType());
    codegen->GetEncoder()->EncodeLdr(dst, false,
                                     MemRef(src, cross_values::GetJsfunctionLexicalEnvOffset(codegen->GetArch())));
    return true;
}

bool ecmascript::HclassCheckGen(HclassCheckInst *checkInst, EncodeVisitor *encV)
{
    auto enc = encV->GetEncoder();
    auto codegen = encV->GetCodegen();
    auto arch = codegen->GetArch();
    auto src = codegen->ConvertRegister(checkInst->GetSrcReg(0), DataType::Type::REFERENCE);
    auto exitLabel = codegen->CreateSlowPath<SlowPathDeoptimize>(checkInst, DeoptimizeType::ANY_TYPE_CHECK)->GetLabel();

    ScopedTmpReg tmpReg(enc, codegen->ConvertDataType(DataType::REFERENCE, arch));
    auto bitMask = codegen->GetRuntime()->GetHClassBitfieldTypeMask(arch);
    auto type = codegen->GetRuntime()->GetJstypeJsFunction(arch);

    if (checkInst->GetCheckIsFunction()) {
        enc->EncodeAnd(tmpReg, src, Imm(bitMask));
        enc->EncodeJump(exitLabel, tmpReg, Imm(type), Condition::NE);
    }

    if (checkInst->GetCheckFunctionIsNotClassConstructor()) {
        auto constructorStartBit = codegen->GetRuntime()->GetJshclassBitfieldClassConstructorStartBit(arch);
        enc->EncodeBitTestAndBranch(exitLabel, src, constructorStartBit, true);
    }

    return true;
}

bool ecmascript::IsDynHeapObject(AnyBaseType type)
{
    return type == compiler::AnyBaseType::ECMASCRIPT_HEAP_OBJECT_TYPE;
}

}  // namespace ark::compiler
